--[[
    stacked_realms, a API that allows modders to easily create stacked realms!
    Copyright (C) 2020  Genshin (emperor_genshin@hotmail.com)

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
--]]

local update_interval = minetest.settings:get("realm_update_interval") or 1

--Realm Registration Table
realms = {
  locations = {},

  --[Method] Set String for Realm Context
  set_string = function(self, string, value)
    assert(string, "context string is missing."..dump(string))
    assert(type(string) == "string", "context string must be a string."..dump(string))
    assert(value or value == "", "context string\'s value is missing."..dump(value))
     if value == "" then --If the value is empty, nil it!
      value = nil
     end
     self[string] = value
   end,
      
   --[Method] Set String for Realm Context
   get_string = function(self, string)
     assert(string, "context string is missing."..dump(string))
     assert(type(string) == "string", "context string must be a string."..dump(string))

     return self[string]
   end

}

--[Global Function]: Register a new realm
function realms.register_realm(realm, def)

  assert(realm, "realm requires a name.".. dump(realm))
  assert(def, "realm \""..realm.."\" does not have a definition table.".. dump(def))
  assert(type(def) == "table", "def is not a table.".. dump(def))

  assert(def.min_height and type(def.min_height) == "number", "min_height is not returning number or it\'s returning nil".. dump(def.min_height))
  assert(def.max_height and type(def.max_height) == "number", "max_height is not returning number or it\'s returning nil".. dump(def.max_height))

  assert(def.skybox_type and type(def.skybox_type) == "string"
  and (def.skybox_type == "default" or def.skybox_type == "colored" or def.skybox_type == "textured"), 
  "skybox_type is not defined or it's not a string, must have a value of default, colored, or textured")

  assert(def.on_realm_entry and type(def.on_realm_entry) == "function", "def requires method \"on_realm_entry\" for realm \""..realm.."\".")
  assert(def.on_realm_entry and type(def.on_realm_leave) == "function", "def requires method \"on_realm_leave\" for realm \""..realm.."\".")

  assert(not realms.locations[realm], "attempt to register a realm that already exist." .. dump(realm))

  realms.locations[realm] = def

  def.in_realm = {} --Table array containing player names of players that are currently in the realm
  def.name = realm
  minetest.log("action", "Realm "..tostring(realm).." is now registered.")
end

--[Global Function]: Get the location name that the player is currently in (returns a string or nil)
function get_player_realm_location(player)

  local name = player:get_player_name()
  local location = nil
  local list = realms.locations

  for k,v in pairs(list)do
   local realm = k
   if list[realm]["in_realm"][name] then
     location = realm
     break
   end
  end

  return location
end

--[Local Function]: Verify registered realms if they don't have matching minimum and max heights. If they do, purge them from their registration table
local function verify_realms() 

  local list = realms.locations
  local to_verify = {}
  local count = 0
  local verified_count = 0

  --First, store their names to a separate table and count them for verification
  for k,v in pairs(list)do
   local realm = k
   local def = v
   count = count + 1
   verified_count = count
   table.insert(to_verify, realm)
  end

  --then do another loop to go through all registered realms and verify conflicting height values
  for k,v in pairs(to_verify) do
    local realm = v
    local data = list[realm]
    local to_count = count

    minetest.log("action", "Verifing Realm "..tostring(realm).." for potential conflicting values before it becomes active.")

    while to_count > 0 do
      local to_compare = to_verify[to_count]
      local comparison = nil

      if list[realm] then
        comparison = list[realm]["name"]
      end

     if comparison and comparison ~= to_compare then --do not compare with yourself
      to_compare = list[to_compare]

      if to_compare == nil then else

        if data.min_height == to_compare.min_height then
          realms.locations[realm] = nil
          verified_count = verified_count - 1
          minetest.log("warning", "Realm with conflicting height values found, removing realm "..tostring(realm).." from the realms registration table to prevent potential errors.")
        elseif data.max_height == to_compare.max_height then
          realms.locations[realm] = nil
          verified_count = verified_count - 1
          minetest.log("warning", "Realm with conflicting height values found, removing realm "..tostring(realm).." from the realms registration table to prevent potential errors.")
        elseif data.min_height == to_compare.max_height or to_compare.max_height == data.min_height then
          realms.locations[realm] = nil
          verified_count = verified_count - 1
          minetest.log("warning", "Realm with conflicting height values found, removing realm "..tostring(realm).." from the realms registration table to prevent potential errors.")
        end

     end

     end

      to_count = to_count - 1
    end

  end

  minetest.log("action", tostring(verified_count).." out of "..tostring(count).." Realms have been verified and ready to activate.")

end


--[Local Function]: MArk the accurate current location by doing a checksum for each realm table
local function in_realm_checksum(player, current_location)

  local name = player:get_player_name()
  local list = realms.locations

  for k,v in pairs(list) do
    local realm_to_check = k
    local to_compare = v["in_realm"]

    if realm_to_check ~= current_location then --Skip checking the current location to compare it's data with the other locations

      for a,b in pairs(to_compare) do
        local found_name = a

        if found_name == name then --If found, remove the player from the old location
          realms.locations[realm_to_check].in_realm[name] = nil
          list[realm_to_check]:on_realm_leave(player)
          break --Stop this loop after the job is completed
        end

      end

    end

  end

  list[current_location]:on_realm_entry(player)

  return
end


--[Local Function]: Realms Handler
local function handle_realms()
  for _, player in ipairs(minetest.get_connected_players()) do

    local name = player:get_player_name()
    local list = realms.locations
    local pos = player:get_pos()
    local update_skybox = false
    local location_data = nil
    local old_location = ""
    local left_realm = false

    for k,v in pairs(list) do
      local realm = k
      local def = v
      local info = def["in_realm"]
      location_data = def

      if (pos.y < def.max_height and pos.y > def.min_height) or (pos.y == def.max_height or pos.y == def.min_height) then --Add player to the realm if the player is currently inside of it's bounds
        if info[name] == nil then
          update_skybox = true
          realms.locations[realm]["in_realm"][name] = true  
          in_realm_checksum(player, realm) 
          minetest.log("action", tostring(name).." is now in "..tostring(realm)..".")
          break
        end

      end

    end

    if update_skybox == true and location_data.skybox_type == "default" then

      player:set_sky(nil, "regular", {})
      player:set_clouds({
        density = 0.4,
        color = "#fff0f0e5",
        ambient = "#000000",
        height = 120,
        thickness = 16,
        speed = {x = 0, y = -2},
      })

      --If player exists, set his physics_override to 0 and notify him and the sender.
      player:set_physics_override({
        speed = minetest.settings:get("movement_speed_walk") or 1,
        jump = 1,
        gravity = 1.0,
        sneak = true,
        sneak_glitch = false,
        new_move = false,
      })

    elseif update_skybox == true and location_data.skybox_type == "colored" then

      player:set_sky(location_data.skybox_color, "plain", {})

      if location_data.enable_clouds == true then

        player:set_clouds({
          density = location_data.cloud_density or 0.4,
          color = location_data.cloud_color or "#fff0f0e5",
          ambient = location_data.cloud_ambient_color or "#000000",
          height = location_data.cloud_height or 120,
          thickness = location_data.cloud_thickness or 16,
          speed = location_data.cloud_speed or {x = 0, y = -2},
        })

      end

      if location_data.realm_physics == true then

        --If player exists, set his physics_override to 0 and notify him and the sender.
        player:set_physics_override({
          speed = minetest.settings:get("movement_speed_walk") or 1,
          jump = 1,
          gravity = location_data.realm_gravity or 1.0,
          sneak = true,
          sneak_glitch = false,
          new_move = false,
        })

      end

    elseif update_skybox == true and location_data.skybox_type == "textured" then

      player:set_sky({}, "skybox", location_data.skybox_textures)

      if location_data.enable_clouds == true then

        player:set_clouds({
          density = location_data.cloud_density or 0.4,
          color = location_data.cloud_color or "#fff0f0e5",
          ambient = location_data.cloud_ambient_color or "#000000",
          height = location_data.cloud_height or 120,
          thickness = location_data.cloud_thickness or 16,
          speed = location_data.cloud_speed or {x = 0, y = -2},
        })

      end

      if location_data.realm_physics == true then

        --If player exists, set his physics_override to 0 and notify him and the sender.
        player:set_physics_override({
          speed = minetest.settings:get("movement_speed_walk") or 1,
          jump = 1,
          gravity = location_data.realm_gravity or 1.0,
          sneak = true,
          sneak_glitch = false,
          new_move = false,
        })

      end

    end

  end

  minetest.after(update_interval, function()
  handle_realms()
  end)
end
						
--Activate cooldown handler
minetest.register_on_mods_loaded(function()
  minetest.after(update_interval, function()
  verify_realms()
  handle_realms()
  end)
end)

minetest.register_on_leaveplayer(function(player)
  local name = player:get_player_name()
  if name then
    player:set_sky({}, "regular", {})

    for k,v in pairs(realms.locations)do
     local realm = k
     if realms.locations[realm]["in_realm"][name] == true then
       realms.locations[realm]["in_realm"][name] = nil
     end
    end
  end
end)
